const { Component } = React;

class VirtualList extends Component {
  constructor(props) {
    super(props);
    this.container = React.createRef(); //容器的引用
    this.extraItemNumber = 2; //额外显示的子项数量
    this.state = {
      containerScrollTop: 0
    };
  }
  componentDidMount() {
    this.handleContainerScrollThrottle = this.throttle(() => {
      this.setState({
        containerScrollTop: this.container.current.scrollTop
      });
    }, 150);
  }

  throttle = (func, wait, options) => {
    var timeout, context, args, result;
    var previous = 0;
    if (!options) options = {};

    var later = function() {
      previous = options.leading === false ? 0 : new Date().getTime();
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };

    var throttled = function() {
      var now = new Date().getTime();
      if (!previous && options.leading === false) previous = now;
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0 || remaining > wait) {
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };

    throttled.cancel = function() {
      clearTimeout(timeout);
      previous = 0;
      timeout = context = args = null;
    };

    return throttled;
  };

  handleContainerScrollThrottle = null;
  //处理容器滚动事件
  handleContainerScroll = e => this.handleContainerScrollThrottle();

  //获取虚拟列表
  getVirtualList() {
    const { extraItemNumber, state, props } = this;
    const { containerScrollTop } = state;
    const { itemHeight, children, containerHeight } = props;

    //进行页面计算
    //计算要显示的第一项的 index ，即比滚动高度隐藏的值小一点的
    //比如 容器高度为150，能显示150/30=5 height 30 top 160 = 160/30  约等于 5.34 ， 即有 5.34 个隐藏了，但是半截还是要显示的，所以从第6个开始显示，数组是从0开始，所以第六个的index是 5
    let firstIndex = Math.floor(containerScrollTop / itemHeight);
    //这个同理，多显示一点的项
    let lastIndex = firstIndex + Math.ceil(containerHeight / itemHeight);
    //如果少于更多项，就从0项开始显示
    firstIndex = Math.max(firstIndex - extraItemNumber, 0);
    //如果显示项超出了，就用数组的长度
    lastIndex = Math.min(lastIndex + extraItemNumber, children.length);

    //根据index获取要显示的项
    const arr = [];
    for (let i = firstIndex; i < lastIndex; i++) {
      arr.push(children[i]);
    }
    return {
      arr,
      top: firstIndex * itemHeight
    };
  }

  render() {
    const { itemHeight, containerHeight, children } = this.props;
    const len = children.length;
    const { arr, top } = this.getVirtualList();

    return (
      <div
        style={{ height: `${containerHeight}px` }}
        className="container"
        ref={this.container}
        onScroll={() => this.handleContainerScroll()}
      >
        <div
          className="holder"
          style={{
            height: itemHeight * len + "px"
          }}
        />
        <div
          className="content"
          style={{
            top: `${top}px`
          }}
        >
          {arr}
        </div>
      </div>
    );
  }
}

class App extends Component {
  constructor(props) {
    super(props);
    this.itemLen = 10000; //生成子项的数量 5w一下都还比较流畅
    this.height = 30; //单项的高度
  }

  genList() {
    const { height, itemLen } = this;
    const arr = [];
    for (let i = 0; i < itemLen; i++) {
      arr.push(
        <div
          key={i}
          style={{
            height: `${height}px`,
            boxSizing: "border-box",
            borderBottom: "1px solid gray"
          }}
        >
          <span>
            <input
              type="checkbox"
              defaultChecked={Math.random() > 0.5 ? "" : "checked"}
            ></input>
            Item {i}
          </span>
        </div>
      );
    }
    return arr;
  }

  render() {
    const props = {
      itemHeight: this.height,
      containerHeight: 150
    };
    return (
      <div style={{ border: "1px solid red" }}>
        <div>子项数量：{this.itemLen}</div>
        <VirtualList {...props}>{this.genList()}</VirtualList>
      </div>
    );
  }
}
ReactDOM.render(<App />, document.getElementById("root"));
